package com.snails.net.restful;


import android.os.AsyncTask;
import android.text.TextUtils;

import com.snails.logger.Logger;
import com.snails.net.SnailsNet;
import com.snails.net.restful.callback.ICompleted;
import com.snails.net.restful.callback.IError;
import com.snails.net.restful.callback.IFailed;
import com.snails.net.restful.callback.IPrepare;
import com.snails.net.restful.callback.ISucceed;
import com.snails.net.restful.common.HttpMethod;
import com.snails.net.restful.common.RestCreator;
import com.snails.net.restful.task.SaveFileTask;
import com.snails.net.retrofit.interceptor.RetrofitConstant;

import java.io.File;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

public final class RestClient {
    private final Builder builder;
    private Call call = null;

    private RestClient(Builder builder) {
        this.builder = builder;
    }

    private void request(HttpMethod method) {
        final RestService service = RestCreator.getRestService();
        if (builder.prepare != null) { builder.prepare.onPrepare(); }
        switch (method) {
            case GET:
                this.call = service.get(builder.url, builder.params, builder.headers);
                break;
            case POST:
                this.call = service.post(builder.url, builder.params, builder.headers);
                break;
            case POST_RAW:
                this.call = service.postRaw(builder.url, builder.body, builder.headers);
                break;
            case PUT:
                this.call = service.put(builder.url, builder.params, builder.headers);
                break;
            case PUT_RAW:
                this.call = service.putRaw(builder.url, builder.body, builder.headers);
                break;
            case DELETE:
                this.call = service.delete(builder.url, builder.params, builder.headers);
                break;
            case UPLOAD:
                closeLogger(); // 上传大文件时，打印日志会导致文件被全部读到内存中
                final MediaType mediaType = MediaType.parse(MultipartBody.FORM.toString());
                final RequestBody requestBody = RequestBody.create(mediaType, builder.file);
                final MultipartBody.Part body = MultipartBody.Part
                        .createFormData("file", builder.file.getName(), requestBody);
                this.call = service.upload(builder.url, body, builder.headers);
                break;
            default:
                break;
        }
        if (this.call != null) { this.call.enqueue(callback); }
    }

    private Callback<String> callback = new Callback<String>() {
        @Override
        public void onResponse(Call<String> call, Response<String> response) {
            try {
                if (response.isSuccessful()) {
                    if (call.isExecuted()) {
                        if (builder.succeed != null) {
                            builder.succeed.onSucceed(response.body());
                        }
                    }
                } else if (builder.error != null) {
                    builder.error.onError(response.code(), response.message());
                }
            } finally {
                if (builder.completed != null) { builder.completed.onCompleted(); }
            }
        }

        @Override
        public void onFailure(Call<String> call, Throwable t) {
            try {
                Logger.e(t, null);
                if (builder.failed != null) { builder.failed.onFailed(); }
            } finally {
                if (builder.completed != null) { builder.completed.onCompleted(); }
            }
        }
    };

    private Callback<ResponseBody> downloadCallback = new Callback<ResponseBody>() {
        @Override
        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
            if (response.isSuccessful()) {
                final ResponseBody responseBody = response.body();
                final SaveFileTask task = new SaveFileTask(SnailsNet.instance().getCtx(),
                        builder.succeed, builder.error, builder.completed);
                task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
                        builder.downloadDir, builder.downloadFile, responseBody);
                //这里一定要注意判断，否则文件下载不全
                if (task.isCancelled()) {
                    if (builder.completed != null) {
                        builder.completed.onCompleted();
                    }
                }
            } else if (builder.error != null) {
                try {
                    builder.error.onError(response.code(), response.message());
                } finally {
                    if (builder.completed != null) { builder.completed.onCompleted(); }
                }
            }
        }

        @Override
        public void onFailure(Call<ResponseBody> call, Throwable t) {
            try {
                if (builder.failed != null) { builder.failed.onFailed(); }
            } finally {
                if (builder.completed != null) { builder.completed.onCompleted(); }
            }
        }
    };

    // 关闭日志，上传下载大文件时，打印日志会导致文件被全部读到内存中
    private void closeLogger() {
        // 上传下载大文件时，打印日志会导致文件被全部读到内存中
        if (builder.headers == null) {
            builder.headers = new HashMap<>();
        }
        builder.headers.put(RetrofitConstant.LOGGER, RetrofitConstant.FALSE);
    }

    public final void get() {
        request(HttpMethod.GET);
    }

    public final void post() {
        if (builder.body == null) {
            request(HttpMethod.POST);
        } else {
            if (!builder.params.isEmpty()) {
                throw new RuntimeException("params must be null!");
            }
            request(HttpMethod.POST_RAW);
        }
    }

    public final void put() {
        if (builder.body == null) {
            request(HttpMethod.PUT);
        } else {
            if (!builder.params.isEmpty()) {
                throw new RuntimeException("params must be null!");
            }
            request(HttpMethod.PUT_RAW);
        }
    }

    public final void delete() {
        request(HttpMethod.DELETE);
    }

    public final void upload() {
        request(HttpMethod.UPLOAD);
    }

    public final void download() {
        if (TextUtils.isEmpty(builder.downloadFile)) {
            throw new NullPointerException("builder.downloadFile不能为空，请调用builder.downloadFile()设置。");
        }
        if (builder.prepare != null) { builder.prepare.onPrepare(); }
        closeLogger(); // 下载大文件时，打印日志会导致文件被全部读到内存中
        final RestService service = RestCreator.getRestService();
        this.call = service.download(builder.url, builder.params, builder.headers);
        if (this.call != null) { this.call.enqueue(downloadCallback); }
    }

    // ================================================================================
    // --- ---
    // ================================================================================
    public static class Builder {
        private String url = "";
        private LinkedHashMap<String, Object> params = new LinkedHashMap<>();
        private Map<String, String> headers = new HashMap<>();
        private File file = null;
        private RequestBody body = null;
        private String downloadDir = "down_loads";
        private String downloadFile = "";

        private ISucceed succeed = null;
        private IError error = null;
        private IFailed failed = null;
        private IPrepare prepare = null;
        private ICompleted completed = null;

        public final Builder url(String url) {
            this.url = url;
            return this;
        }

        public final Builder params(Map<String, Object> params) {
            if (params != null && params.size() > 0) {
                this.params.putAll(params);
            }
            return this;
        }

        public final Builder params(String key, Object value) {
            if (!TextUtils.isEmpty(key)) {
                this.params.put(key, value);
            }
            return this;
        }

        public final Builder headers(Map<String, String> headers) {
            if (headers != null && headers.size() > 0) {
                this.headers.putAll(headers);
            }
            return this;
        }

        public final Builder headers(String key, String value) {
            if (!TextUtils.isEmpty(key)) {
                this.headers.put(key, value);
            }
            return this;
        }

        public final Builder headers(String key, Object value) {
            if (!TextUtils.isEmpty(key)) {
                this.headers.put(key, value == null ? "" : String.valueOf(value));
            }
            return this;
        }

        public final Builder file(File file) {
            this.file = file;
            return this;
        }

        public final Builder file(String file) {
            if (!TextUtils.isEmpty(file)) {
                this.file = new File(file);
            }
            return this;
        }

        public final Builder downloadFile(String dir, String fileName) {
            this.downloadDir = dir;
            if (TextUtils.isEmpty(fileName)) {
                throw new NullPointerException("fileName 不能为空.");
            } else {
                this.downloadFile = fileName;
            }
            return this;
        }

        public final Builder raw(String raw) {
            this.body = RequestBody.create(MediaType.parse("application/json;charset=UTF-8"), raw);
            return this;
        }

        public final Builder succeed(ISucceed succeed) {
            this.succeed = succeed;
            return this;
        }

        public final Builder error(IError error) {
            this.error = error;
            return this;
        }

        public final Builder failed(IFailed failed) {
            this.failed = failed;
            return this;
        }

        public final Builder prepare(IPrepare prepare) {
            this.prepare = prepare;
            return this;
        }

        public final Builder completed(ICompleted completed) {
            this.completed = completed;
            return this;
        }

        public final RestClient build() {
            return new RestClient(this);
        }
    }
}
